/**
 * ****************************************************************************
 * Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ****************************************************************************
 */
package com.cloudlead.common.worker.client;

import com.cloudlead.common.worker.client.exception.RestClientException;
import com.cloudlead.common.worker.client.exception.RestClientIOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;

/**
 * Creates all the HTTP requests needed for the RestClient and handles the HTTP
 * responses.
 *
 * @author yael
 */
public class RestClientExecutor {
    private final static org.slf4j.Logger logger = LoggerFactory
            .getLogger(RestClientExecutor.class);

    private static final String FORWARD_SLASH = "/";

    private final HttpClient httpClient;
    private String urlStr;

    /**
     * C'tor.
     * <p>
     *
     * @param url .
     */
    public RestClientExecutor(final URL url) {
        this.httpClient = getHttpClient();

        this.urlStr = url.toExternalForm();
        if (!this.urlStr.endsWith(FORWARD_SLASH)) {
            this.urlStr += FORWARD_SLASH;
        }
    }

    /**
     * 适合多线程的HttpClient
     *
     * @return HttpClient
     */
    public static HttpClient getHttpClient() {
        HttpParams params = new BasicHttpParams();
        //设置连接超时时间
        HttpConnectionParams.setConnectionTimeout(params, Constants.DEFAULT_HTTP_CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(params, Constants.DEFAULT_HTTP_READ_TIMEOUT);

        //多连接的线程安全的管理器
        PoolingClientConnectionManager pccm = new PoolingClientConnectionManager();
        pccm.setDefaultMaxPerRoute(Constants.DEFAULT_MAX_PER_ROUTE);    //每个主机的最大并行链接数
        pccm.setMaxTotal(Constants.MAX_TOTAL);            //客户端总并行链接最大数

        DefaultHttpClient httpClient = new DefaultHttpClient(pccm, params);
        httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler());
        return httpClient;
    }

    /**
     * Executes HTTP post over REST on the given (relative) URL with the given
     * body.
     *
     * @param url  The URL to post to.
     * @param body The content of the post.
     * @return The response object from the REST server.
     * @throws RestClientException Reporting failure to post the file.
     */
    public String post(final String url, final String body) throws RestClientException {
        final HttpEntity stringEntity;
        String jsonStr;
        try {
            stringEntity = new StringEntity(body, "UTF-8");
        } catch (final IOException e) {
            throw new RestClientIOException("not support encode error.", e);
        }
        logger.debug("executing post request to " + url + ", tring to post object " + body);
        return post(url, stringEntity);
    }

    /**
     * Executes HTTP put over REST on the given (relative) URL with the given
     * body.
     *
     * @param url  The URL to post to.
     * @param body The content of the post.
     * @return The response object from the REST server.
     * @throws RestClientException Reporting failure to post the file.
     */
    public String put(final String url, final String body) throws RestClientException {
        final HttpEntity stringEntity;
        String jsonStr;
        try {
            stringEntity = new StringEntity(body, "UTF-8");
        } catch (final IOException e) {
            throw new RestClientIOException("not support encode error.", e);
        }
        logger.debug("executing put request to " + url + ", tring to put object " + body);
        return put(url, stringEntity);
    }

    /**
     * @param relativeUrl The URL to post to.
     * @param fileToPost  The file to post.
     * @param partName    The name of the request parameter (the posted file) to bind
     *                    to.
     * @return The response object from the REST server.
     * @throws RestClientException Reporting failure to post the file.
     */
    public String postFile(final String relativeUrl, final File fileToPost, final String partName) throws RestClientException {

        MultipartEntity reqEntity = new MultipartEntity();
        reqEntity.addPart(partName, new FileBody(fileToPost));
        logger.debug("executing post request to " + relativeUrl + ", tring to post file " + fileToPost.getName());
        return post(relativeUrl, reqEntity);
    }

    /**
     * @param relativeUrl The URL to send the get request to.
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    public String get(final String relativeUrl) throws RestClientException {
        String fullUrl = getFullUrl(relativeUrl);
        final HttpGet getRequest = new HttpGet(fullUrl);
        logger.debug("execute get request to " + relativeUrl);
        return executeRequest(getRequest);
    }

    /**
     * @param relativeUrl The URL to send the get request to.
     * @param params      Request parameters
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    public String get(final String relativeUrl, final Map<String, String> params) throws RestClientException {
        URIBuilder builder;

        try {
            builder = new URIBuilder(getFullUrl(relativeUrl));
        } catch (URISyntaxException e) {
            throw new RestClientException("URL syntax error.", e);
        }

        if (params != null) {
            for (final Map.Entry<String, String> entry : params.entrySet()) {
                builder.addParameter(entry.getKey(), entry.getValue());
            }
        }
        final HttpGet getRequest = new HttpGet(builder.toString());
        logger.debug("execute get request to " + relativeUrl);
        return executeRequest(getRequest);
    }

    /**
     * @param relativeUrl The URL to send the delete request to.
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    public String delete(final String relativeUrl) throws RestClientException {

        final HttpDelete deleteRequest = new HttpDelete(getFullUrl(relativeUrl));
        logger.debug("executing delete request to " + relativeUrl);
        return executeRequest(deleteRequest);
    }

    /**
     * @param relativeUrl The URL to send the delete request to.
     * @param params      Request parameters
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    public String delete(final String relativeUrl, final Map<String, String> params) throws RestClientException {

        URIBuilder builder;

        try {
            builder = new URIBuilder(getFullUrl(relativeUrl));
        } catch (URISyntaxException e) {
            throw new RestClientException("URL syntax error.", e);
        }

        if (params != null) {
            for (final Map.Entry<String, String> entry : params.entrySet()) {
                builder.addParameter(entry.getKey(), entry.getValue());
            }
        }

        final HttpDelete deleteRequest = new HttpDelete(builder.toString());
        logger.debug("executing delete request to " + relativeUrl);
        return executeRequest(deleteRequest);
    }

    /**
     * @param relativeUrl The URL to send the delete request to.
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    private String post(final String relativeUrl, final HttpEntity entity) throws RestClientException {
        final HttpPost postRequest = new HttpPost(getFullUrl(relativeUrl));
        if (entity instanceof StringEntity) {
            postRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
        }
        postRequest.setEntity(entity);
        return executeRequest(postRequest);
    }

    /**
     * @param relativeUrl The URL to send the delete request to.
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    private String put(final String relativeUrl, final HttpEntity entity) throws RestClientException {
        final HttpPut putRequest = new HttpPut(getFullUrl(relativeUrl));
        if (entity instanceof StringEntity) {
            putRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
        }
        putRequest.setEntity(entity);
        return executeRequest(putRequest);
    }

    private String executeRequest(final HttpRequestBase request) throws RestClientException {
        HttpResponse httpResponse;
        try {
            try {
                httpResponse = httpClient.execute(request);
            } catch (IOException e) {
                logger.error("Failed executing " + request.getMethod() + " request to " + request.getURI() + " : " + e.getMessage());
                throw new RestClientIOException("Failed executing " + request.getMethod() + " request to " + request.getURI() + " : " + e.getMessage(), e);
            }
            return getResponseBody(httpResponse);
        } finally {
            request.abort();
        }
    }

    /**
     * Return the response's body.
     *
     * @param response .
     * @return the response's body.
     * @throws RestClientIOException if failed to transform the response into string.
     */
    static String getResponseBody(final HttpResponse response) throws RestClientIOException {

        final HttpEntity entity = response.getEntity();
        if (entity == null) {
            return null;
        }
        try {
            return EntityUtils.toString(entity, "utf-8");
        } catch (IOException e) {
            e.printStackTrace();
            throw new RestClientIOException("Get response entity string error.", e);
        }
    }


    /**
     * Appends the given relative URL to the basic rest-service URL.
     *
     * @param relativeUrl URL to add to the basic URL
     * @return full URL as as String
     */
    private String getFullUrl(final String relativeUrl) {
        String safeRelativeURL = relativeUrl;
        if (safeRelativeURL.startsWith(FORWARD_SLASH)) {
            safeRelativeURL = safeRelativeURL.substring(1);
        }
        return urlStr + safeRelativeURL;
    }

}
